﻿using System;
using SoftwareConsulting.BI11.Studio.InternalServices;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra;

namespace SoftwareConsulting.BI11.NumericalMethods.Regression
{
    /// <summary>
    /// Класс полиномиального МНК
    /// </summary>
    public class PolynomialLeastSquareFit
    {
        private double[,] _systemMatrix;//матрица системы
        private double[] _systemConstants;//константы системы

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="n"></param>
        public PolynomialLeastSquareFit(int n)
        {
            int n1 = n + 1;
            _systemMatrix = new double[n1, n1];
            _systemConstants = new double[n1];
            Reset();
        }

        /// <summary>
        /// Сохраняет среднее число X
        /// </summary>
        /// <param name="x"></param>
        /// <param name="statisticalMoments"></param>
        public void AccumulateAverage(double x, StatisticalMoments statisticalMoments)
        {
            AccumulatePoint(x, statisticalMoments.Average, statisticalMoments.ErrorOnAverage);
        }

        /// <summary>
        /// Сохраняет значение интервала гистограммы
        /// </summary>
        /// <param name="x"></param>
        /// <param name="binContent"></param>
        public void AccumulateBin(double x, int binContent)
        {
            AccumulateWeightedPoint(x, binContent, 1.0 / Math.Max(1, binContent));
        }

        /// <summary>
        /// Сохраняет точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void AccumulatePoint(double x, double y)
        {
            AccumulateWeightedPoint(x, y, 1);
        }

        /// <summary>
        /// Сохраняет точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="standardDeviation">стандартное отклонение по Y</param>
        public void AccumulatePoint(double x, double y, double standardDeviation)
        {
            AccumulateWeightedPoint(x, y, 1.0 / (standardDeviation * standardDeviation));
        }

        /// <summary>
        /// Сохраняет взвешенную точку
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="weight"></param>
        public void AccumulateWeightedPoint(double x, double y, double weight)
        {
            double xp1 = weight;            
            for (int i = 0; i < _systemConstants.Length; i++)
            {
                _systemConstants[i] += xp1 * y;
                double xp2 = xp1;
                for (int j = 0; j <= i; j++)
                {
                    _systemMatrix[i, j] += xp2;
                    xp2 *= x;
                }
                xp1 *= x;
            }
        }

        /// <summary>
        /// Вычисляет, используя МНК
        /// </summary>
        /// <returns>оцененный полином</returns>
        public EstimatedPolynomial Evaluate()
        {
            for (int i = 0; i < _systemConstants.Length; i++)
            {
                for (int j = i + 1; j < _systemConstants.Length; j++)
                    _systemMatrix[i, j] = _systemMatrix[j, i];
            }
            try
            {
                LowerUpperPermutationDecomposition lupSystem = new LowerUpperPermutationDecomposition(_systemMatrix);
                double[,] components = lupSystem.GetInversedMatrixComponents();
                LowerUpperPermutationDecomposition.SymmetrizeComponents(components);
                return new EstimatedPolynomial(lupSystem.GetSolution(_systemConstants), SymmetricMatrix.FromComponents(components));
            }
            catch (BIMatrixIllegalDimensionException ex)
            {
                Trace.StartBlock("BIMatrixIllegalDimensionException killed");
                Trace.Message("BIMatrixIllegalDimensionException catched and silently killed in PolynomialLeastSquareFit::Evaluate! Exception text:");
                Trace.Message(ex.ToString());
                Trace.EndBlock();
            }
            catch (BIMatrixNonSymmetricComponentsException ex)
            {
                Trace.StartBlock("BIMatrixNonSymmetricComponentsException killed");
                Trace.Message("BIMatrixNonSymmetricComponentsException catched and silently killed in PolynomialLeastSquareFit::Evaluate! Exception text:");
                Trace.Message(ex.ToString());
                Trace.EndBlock();
            }
            return null;
        }

        /// <summary>
        /// Сбрасывает всё
        /// </summary>
        public void Reset()
        {
            for (int i = 0; i < _systemConstants.Length; i++)
            {
                _systemConstants[i] = 0;
                for (int j = 0; j < _systemConstants.Length; j++)
                    _systemMatrix[i, j] = 0;
            }
        }
    }
}
